DataGrid - Fix the browser scrolling back to the grid when data is updated (T1310557)#33168
DataGrid - Fix the browser scrolling back to the grid when data is updated (T1310557)#33168markallenramirez wants to merge 7 commits intoDevExpress:26_1from
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes DataGrid keyboard-navigation focus restoration so that updating data doesn’t cause the browser to scroll the page back to the grid (by using native focus({ preventScroll }) during render completion).
Changes:
- Add a
preventScrollflag to_updateFocusand_focusand propagate it fromrenderCompleted. - Replace synthetic
eventsEngine.trigger(..., 'focus')with native DOMfocus({ preventScroll })for the focused cell/row.
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
Outdated
Show resolved
Hide resolved
a9afe9f to
d2b9704
Compare
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
Outdated
Show resolved
Hide resolved
9ff69ab to
8b4814d
Compare
packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
Show resolved
Hide resolved
4080b45 to
6d35176
Compare
| $('body').css('height', 2000); | ||
| window.scrollTo(0, 0); | ||
| this.clock.tick(10); | ||
|
|
||
| const keyboardController = dataGrid.getController('keyboardNavigation'); | ||
| const $firstCell = $(dataGrid.getCellElement(0, 0)); | ||
| const scrollPosition = 300; | ||
|
|
||
| // assert | ||
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is at the top'); | ||
|
|
||
| // act | ||
| $firstCell.trigger(CLICK_EVENT); | ||
| this.clock.tick(10); | ||
|
|
||
| keyboardController.keyDownHandler({ | ||
| key: 'Tab', | ||
| keyName: 'tab', | ||
| originalEvent: $.Event('keydown', { target: $firstCell.get(0) }), | ||
| }); | ||
| this.clock.tick(10); | ||
|
|
||
| // assert | ||
| assert.ok($(dataGrid.getCellElement(0, 1)).hasClass('dx-focused'), 'second cell is focused'); | ||
|
|
||
| // act | ||
| window.scrollTo(0, scrollPosition); | ||
|
|
||
| // assert | ||
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is changed'); | ||
|
|
||
| // act | ||
| dataGrid.getDataSource().store().push([{ type: 'update', key: 1, data: { name: 'updated' } }]); | ||
| this.clock.tick(10); | ||
|
|
||
| // assert | ||
| assert.strictEqual($(dataGrid.getCellElement(0, 1)).text(), 'updated', 'second cell text is updated'); | ||
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after push update'); | ||
|
|
||
| // act | ||
| dataGrid.refresh(); | ||
| this.clock.tick(10); | ||
|
|
||
| // assert | ||
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after refresh'); | ||
|
|
||
| // act | ||
| dataGrid.repaint(); | ||
| this.clock.tick(10); | ||
|
|
||
| // assert | ||
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after repaint'); | ||
|
|
||
| // act | ||
| keyboardController.keyDownHandler({ | ||
| key: 'ArrowLeft', | ||
| keyName: 'leftArrow', | ||
| originalEvent: $.Event('keydown', { target: $(dataGrid.getCellElement(0, 1)).get(0) }), | ||
| }); | ||
| this.clock.tick(10); | ||
|
|
||
| // assert | ||
| assert.ok($(dataGrid.getCellElement(0, 0)).hasClass('dx-focused'), 'first cell is focused'); | ||
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is changed after keyboard navigation'); | ||
|
|
||
| // cleanup | ||
| $('body').css('height', ''); | ||
| window.scrollTo(0, 0); |
There was a problem hiding this comment.
This test mutates global page state (body height and window scroll position) without a try/finally-style guard. If an exception occurs before the cleanup section, subsequent tests can run with a modified document height/scroll position. Consider capturing the initial body height/scroll position and restoring them in a finally block (or using an appended temporary spacer element like other scrolling-related tests do).
| $('body').css('height', 2000); | |
| window.scrollTo(0, 0); | |
| this.clock.tick(10); | |
| const keyboardController = dataGrid.getController('keyboardNavigation'); | |
| const $firstCell = $(dataGrid.getCellElement(0, 0)); | |
| const scrollPosition = 300; | |
| // assert | |
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is at the top'); | |
| // act | |
| $firstCell.trigger(CLICK_EVENT); | |
| this.clock.tick(10); | |
| keyboardController.keyDownHandler({ | |
| key: 'Tab', | |
| keyName: 'tab', | |
| originalEvent: $.Event('keydown', { target: $firstCell.get(0) }), | |
| }); | |
| this.clock.tick(10); | |
| // assert | |
| assert.ok($(dataGrid.getCellElement(0, 1)).hasClass('dx-focused'), 'second cell is focused'); | |
| // act | |
| window.scrollTo(0, scrollPosition); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is changed'); | |
| // act | |
| dataGrid.getDataSource().store().push([{ type: 'update', key: 1, data: { name: 'updated' } }]); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual($(dataGrid.getCellElement(0, 1)).text(), 'updated', 'second cell text is updated'); | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after push update'); | |
| // act | |
| dataGrid.refresh(); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after refresh'); | |
| // act | |
| dataGrid.repaint(); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after repaint'); | |
| // act | |
| keyboardController.keyDownHandler({ | |
| key: 'ArrowLeft', | |
| keyName: 'leftArrow', | |
| originalEvent: $.Event('keydown', { target: $(dataGrid.getCellElement(0, 1)).get(0) }), | |
| }); | |
| this.clock.tick(10); | |
| // assert | |
| assert.ok($(dataGrid.getCellElement(0, 0)).hasClass('dx-focused'), 'first cell is focused'); | |
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is changed after keyboard navigation'); | |
| // cleanup | |
| $('body').css('height', ''); | |
| window.scrollTo(0, 0); | |
| const $body = $('body'); | |
| const initialBodyHeight = $body.css('height'); | |
| const initialScrollLeft = window.pageXOffset; | |
| const initialScrollTop = window.pageYOffset; | |
| $body.css('height', 2000); | |
| window.scrollTo(0, 0); | |
| this.clock.tick(10); | |
| try { | |
| const keyboardController = dataGrid.getController('keyboardNavigation'); | |
| const $firstCell = $(dataGrid.getCellElement(0, 0)); | |
| const scrollPosition = 300; | |
| // assert | |
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is at the top'); | |
| // act | |
| $firstCell.trigger(CLICK_EVENT); | |
| this.clock.tick(10); | |
| keyboardController.keyDownHandler({ | |
| key: 'Tab', | |
| keyName: 'tab', | |
| originalEvent: $.Event('keydown', { target: $firstCell.get(0) }), | |
| }); | |
| this.clock.tick(10); | |
| // assert | |
| assert.ok($(dataGrid.getCellElement(0, 1)).hasClass('dx-focused'), 'second cell is focused'); | |
| // act | |
| window.scrollTo(0, scrollPosition); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is changed'); | |
| // act | |
| dataGrid.getDataSource().store().push([{ type: 'update', key: 1, data: { name: 'updated' } }]); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual($(dataGrid.getCellElement(0, 1)).text(), 'updated', 'second cell text is updated'); | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after push update'); | |
| // act | |
| dataGrid.refresh(); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after refresh'); | |
| // act | |
| dataGrid.repaint(); | |
| this.clock.tick(10); | |
| // assert | |
| assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after repaint'); | |
| // act | |
| keyboardController.keyDownHandler({ | |
| key: 'ArrowLeft', | |
| keyName: 'leftArrow', | |
| originalEvent: $.Event('keydown', { target: $(dataGrid.getCellElement(0, 1)).get(0) }), | |
| }); | |
| this.clock.tick(10); | |
| // assert | |
| assert.ok($(dataGrid.getCellElement(0, 0)).hasClass('dx-focused'), 'first cell is focused'); | |
| assert.strictEqual(window.pageYOffset, 0, 'document scroll is changed after keyboard navigation'); | |
| } finally { | |
| $body.css('height', initialBodyHeight); | |
| window.scrollTo(initialScrollLeft, initialScrollTop); | |
| } |
| // T1310557 | ||
| QUnit.testInActiveWindow('Browser should not scroll back to the grid when a focused cell is updated or rerendered', function(assert) { | ||
| // arrange |
There was a problem hiding this comment.
This scrolling/focus regression test is currently placed under the "Column Resizing" module, but it doesn't exercise resizing behavior. Moving it to a more relevant module (e.g., "View's focus" or a dedicated scrolling/focus module) will make the suite easier to navigate and maintain.
No description provided.